package com.wry.wallet.service.impl;

import com.wry.wallet.annotation.Flow;
import com.wry.wallet.entity.Account;
import com.wry.wallet.mapper.AccountFlowMapper;
import com.wry.wallet.mapper.AccountMapper;
import com.wry.wallet.service.WalletService;
import com.wry.wallet.vo.AccountFlowVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static com.wry.wallet.enums.DealTypeEnum.*;

/**
 * <h1>  </h1>
 *
 *
 * @author wry
 * @since 2023.03.31
 */
@Slf4j
@Service
public class WalletServiceImpl implements WalletService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private AccountMapper accountMapper;
    @Resource
    private AccountFlowMapper accountFlowMapper;

    private final AtomicInteger COUNTER = new AtomicInteger(0);

    private static final String LOCK = "wallet:account:lock";
    private static final String KEY = "wallet:account:key";

    @Override
    public BigDecimal queryBalance(String accountNumber) {
        Account account = accountMapper.selectUserByAccountNumber(accountNumber);
        return account.getBalance();
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    @Flow(type = CONSUME)
    @Override
    public void consume(String accountNumber, BigDecimal amount) {
        //todo: 需要考虑：1.扣款查询余额后,还未扣款；此时收到退款。2.退款查询余额后,还未退款，此时账户发生扣款  如何update你的余额
       Boolean lock;
       try {
           //todo： 3s后自动释放锁，此处要根据实际场景合理设计；也可以直接上Redission
           lock = redisTemplate.opsForValue().setIfAbsent(LOCK, KEY, 3000L, TimeUnit.MILLISECONDS);
           if (lock){
               Account account = accountMapper.selectUserByAccountNumber(accountNumber);
               if (account.getBalance().compareTo(amount) < 0){
                   throw new RuntimeException("余额不足！");
               }
               BigDecimal currentBalance = account.getBalance().subtract(amount);
               account.setBalance(currentBalance);
               account.setUpdatedAt(LocalDateTime.now());
               accountMapper.updateById(account);
               COUNTER.set(0);
               // todo：之后可以处理其他业务，若希望通知用户，可以上消息队列发送异步通知；后续代码可以不加锁了
               // 其他业务操作
               Thread.sleep(50L);
               return;
           }
           // 重试机制
           COUNTER.getAndAdd(1);
           if (COUNTER.get() > 10){
               COUNTER.set(0);
               throw new RuntimeException("交易超时，请重试");
           }
           Thread.sleep(300L);
           consume(accountNumber, amount);
       }catch (RuntimeException e){
           e.printStackTrace();
           throw e;
       }catch (InterruptedException ignored){}
       finally {
           redisTemplate.delete(LOCK);
       }
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    @Flow(type = REFUND)
    @Override
    public void refund(String accountNumber, BigDecimal amount) {
        // 退款拿到锁和扣款时为同一把锁，与扣款操作互斥
        // 先退款再扣款 或者 先扣款再退款
        Boolean lock;
        try {
            lock = redisTemplate.opsForValue().setIfAbsent(LOCK, KEY, 3000L, TimeUnit.MILLISECONDS);
            if (lock){
                Account account = accountMapper.selectUserByAccountNumber(accountNumber);
                BigDecimal currentBalance = account.getBalance().add(amount);
                account.setUpdatedAt(LocalDateTime.now());
                account.setBalance(currentBalance);
                accountMapper.updateById(account);
                COUNTER.set(0);
                // todo：同理 之后可以处理其他业务，若希望通知用户，可以上消息队列发送异步通知；后续代码可以不加锁了
                // 其他业务操作
                Thread.sleep(50L);
                return;
            }
            COUNTER.getAndAdd(1);
            if (COUNTER.get() > 10){
                COUNTER.set(0);
                throw new RuntimeException("退款超时，请重试");
            }
            Thread.sleep(300L);
            refund(accountNumber, amount);
        }catch (RuntimeException e){
            e.printStackTrace();
            throw e;
        }catch (InterruptedException ignored){}
        finally {
            redisTemplate.delete(LOCK);
        }
    }

    @Override
    public List<AccountFlowVO> flow(String accountNumber) {
        return accountFlowMapper.selectFlowByAccountNumber(accountNumber);
    }
}
